package com.totoro.tyrank.manager;

import com.totoro.tyrank.TyRank;
import com.totoro.tyrank.constants.PatternConst;
import com.totoro.tyrank.constants.PlayerIdentityConst;
import com.totoro.tyrank.constants.StaticSystemVarConst;
import com.totoro.tyrank.constants.TagConst;
import com.totoro.tyrank.data.*;
import com.totoro.tyrank.entity.TopRangeResult;
import com.totoro.tyrank.enums.ActionEnum;
import com.totoro.tyrank.executor.action.ActionExecutor;
import com.totoro.tyrank.handle.SkullCacheHandler;
import com.totoro.tyrank.utils.*;
import me.clip.placeholderapi.PlaceholderAPI;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;

import java.util.*;
import java.util.regex.Matcher;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class RankingTaskManager {

    private final TyRank plugin;
    private final Map<String, RankingTaskData> rankingTasks = new HashMap<>();


    public RankingTaskManager(TyRank plugin) {
        this.plugin = plugin;
    }

    // 加载所有排行榜并启动定时任务
    public void reloadRankingsTask() {
        // 先停止所有已存在的任务
        stopAllTasks();
        rankingTasks.clear();
        // 获取所有排行榜,执行异步任务
        Map<String, RankingData> rankDataMap = TyRank.getRankingManager().getRankDataMap();
        for (Map.Entry<String, RankingData> entry : rankDataMap.entrySet()) {
            long updateInterval = entry.getValue().getUpdateInterval();
            RankingTaskData rankingTaskData = new RankingTaskData(entry.getKey(), updateInterval);
            rankingTasks.put(entry.getKey(), rankingTaskData);
            startRankingTask(rankingTaskData);
        }
    }

    /**
     * @author totoro
     * @description 为单个排行榜启动定时更新任务
     * @date  9:24
     * @param [rankingTask]
     * @return void
     **/
    private void startRankingTask(RankingTaskData rankingTask) {
        // 取消可能存在的旧任务
        if (rankingTask.getTaskId() != -1) {
            Bukkit.getScheduler().cancelTask(rankingTask.getTaskId());
        }
        // 判断当前任务时间是否已经结束
        Map<String, RankingData> rankDataMap = TyRank.getRankingManager().getRankDataMap();
        if (! rankDataMap.containsKey(rankingTask.getRankId())) {
            rankingTasks.remove(rankingTask.getRankId());
            return;
        }
        RankingData rankingData = rankDataMap.get(rankingTask.getRankId());
        // 判断是否闭榜
        if(TyRank.getRankDataManager().getRankDataMap().get(rankingData.getId()).getIsEnd()) {
            return;
        }
        // 没有闭榜，刷新更改时间并且isEnd永远为false
        RankData rankData = new RankData(rankingData.getId(), false, DateUtil.currentDate());
        TyRank.getRankDataManager().getRankDataMap().put(rankingData.getId(), rankData);
        TyRank.getRankDataManager().saveRankData(Stream.of(rankData).collect(Collectors.toList()));

        // 转换秒为tick (20 tick = 1秒)
        long intervalTicks = rankingTask.getUpdateInterval() * 20L;

        // 异步执行更新任务，避免阻塞主线程
        int taskId = Bukkit.getScheduler().runTaskTimerAsynchronously(plugin, () -> updateRanking(rankingData), 0L, intervalTicks).getTaskId();
        rankingTask.setTaskId(taskId);
        rankingTasks.put(rankingData.getId(), rankingTask);
        plugin.getServer().getConsoleSender().sendMessage(StaticSystemVarConst.CONSOLE_PREFIX +
                ColorUtil.color("&c排行榜: &e[" + rankingTask.getRankId() + "] &c 已启动，更新间隔为: &e[" + rankingTask.getUpdateInterval() + "秒]"));
    }

    // 更新排行榜数据的实际逻辑
    private void updateRanking(RankingData rankingData) {
        // 获取该排行榜配置
        if (!TyRank.getRankingManager().getRankDataMap().containsKey(rankingData.getId())) {
            plugin.getServer().getConsoleSender().sendMessage(StaticSystemVarConst.CONSOLE_PREFIX +
                    ColorUtil.color("&c排行榜: &e[" + rankingData.getId() + "] &c数据更新失败，该排行榜不存在..."));
            return;
        }
        try {
            // 如果当前时间大于该排行榜闭榜时间
            boolean currentTimeAfter = DateUtil.isCurrentTimeAfter(rankingData.getEndTime().format(StaticSystemVarConst.DATE_FORMATTER));
            if(currentTimeAfter) {
                doRankEndAction(rankingData);
            }
            plugin.getServer().getConsoleSender().sendMessage(StaticSystemVarConst.CONSOLE_PREFIX +
                    ColorUtil.color(String.format("&c正在更新排行榜: &e[%s] &c数据...", rankingData.getId())));
            /* 07/27 4:08 - TyRank跨服数据库模式的问题：PAPI是无法解析离线玩家的变量，更何况还是数据共享的云服，但也不至于再加一个Redis去同步数据。
            唯一解决办法：先获取数据库中的保存的当前排行ID的玩家并转化为PlayerSortData，
            然后再获取当前在线玩家解析一次变量后并转化为PlayerSortData，要用Map去重，不能是Set，这样子排序就会比较正确了，
            至于不在线的玩家确实是获取不到变量，那么则可以再新建一个异步任务每隔几分钟读取在config中设置的清榜间隔时间，
            然后根据这个时间去判断多久没有上线且更新过排行榜的玩家直接清榜
            (将当前时间减去该玩家数据对象的lastUpdateTime，如果大于设置的时间则清除该玩家的榜数据)，
            防止BUG出现，也不用担心不同服务器之间共享排行榜的读取了 */
            // 排行榜更新规则：
            // 获取数据库或本地文件中所有关于该排行榜的玩家，再加上当前在线的玩家(新玩家也需要加入排行榜)，要求Set因为去重
            // 刷新排行榜玩家数据
            TyRank.getPlayerDataManager().loadRankPlayerData(rankingData.getId());
            Map<String, LinkedList<PlayerData>> playersDataMap = TyRank.getPlayerDataManager().getPlayersDataMap();
            // 获取数据库中玩家的数据转换为PlayerSortData的Map对象
            Map<String, PlayerSortData> playerSortDataMap = playersDataMap.get(rankingData.getId()).stream().collect(Collectors.toMap(
                    PlayerData::getPlayerIdentity,
                    playerData -> new PlayerSortData(playerData.getPlayerIdentity(), NumberUtil.toNumber(playerData.getCurrentValue()), playerData.getRewardReceive(), playerData.getUpdateDateTime()),
                    (existing, replacement) -> replacement));
            // 获取当前在线玩家 - 解析当前在线玩家的变量并存入上方playerSortDataMap中，优先用在线玩家新解析的变量去替换掉之前保存的变量
            for (Player player : Bukkit.getOnlinePlayers()) {
                String papiResult = PlaceholderAPI.setPlaceholders(player, rankingData.getRankBy());
                Number number = NumberUtil.toNumber(papiResult);
                // 获取当前在线玩家是否有过数据了，需要获取是否领取字段和是否清除字段
                PlayerData playerData = TyRank.getPlayerDataManager().getPlayerDataByRankId(player, rankingData.getId());
                Boolean rewardReceive = false;
                if(null != playerData) {
                    rewardReceive = playerData.getRewardReceive();
                }
                PlayerSortData playerSortData = new PlayerSortData(TyRank.getConfigManager().getConfigData().getPlayerIdentity().equals(PlayerIdentityConst.PLAYER_UUID)
                        ? player.getUniqueId().toString() : player.getName(), number, rewardReceive, DateUtil.currentDateStr());
                playerSortDataMap.put(playerSortData.getPlayerIdentity(), playerSortData);
            }
            // 将上方组装好的所有玩家(在线数据+数据库数据)转换为List进行排序
            LinkedList<PlayerSortData> playerSortBeforeDataList = ListUtil.sortPlayerData(new ArrayList<>(playerSortDataMap.values()));
            // 存入数据库或文件，将该集合转换成最终的PlayerData集合，就可以继续持久化至文件或者数据库了
            List<PlayerData> playerDataList = new ArrayList<>(playerSortBeforeDataList.size());
            for (int i = 0; i < playerSortBeforeDataList.size(); i++) {
                // 如果当前循环统计次数等于该榜最大统计次数，则结束循环
                if(rankingData.getMaxStatistics() != -1 && (i+1) >= rankingData.getMaxStatistics()) {
                    break;
                }
                PlayerSortData playerSortData = playerSortBeforeDataList.get(i);
                PlayerData playerData = new PlayerData(playerSortData.getPlayerIdentity(), rankingData.getId(), i+1,
                        String.format(rankingData.getValueFormat(), playerSortData.getNumber().doubleValue()), playerSortData.getRewardReceive(), playerSortData.getUpdateDateTime(),
                        DateUtil.isTimeExceeded(playerSortData.getUpdateDateTime(), rankingData.getClearInterval()));
                playerDataList.add(playerData);
            }
            // 文件存入规则：直接调用set即可
            // 数据库存入规则：1.删除所有关于该rank的数据并重新insert(X)  2.根据玩家标识和排行id查询再根据结果进行insert或者update (√)
            TyRank.getPlayerDataManager().saveRankPlayerData(rankingData.getId(), playerDataList);

            plugin.getServer().getConsoleSender().sendMessage(StaticSystemVarConst.CONSOLE_PREFIX +
                    ColorUtil.color(String.format("&c排行榜 &e[%s] &c的数据更新完毕，本次共更新了 &e%s &c个玩家的数据", rankingData.getId(), playerDataList.size())));
            TyRank.getAttributeManager().loadOnlinePlayerAttribute();
            // 如果闭榜了
            if(currentTimeAfter) {
                // 清除该榜任务
                if (rankingTasks.containsKey(rankingData.getId()) && rankingTasks.get(rankingData.getId()).getTaskId() != -1) {
                    Bukkit.getScheduler().cancelTask(rankingTasks.get(rankingData.getId()).getTaskId());
                }
                rankingTasks.remove(rankingData.getId());
            }
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("排行榜" + rankingData.getId() + "数据更新失败，请检查PAPI变量值等等...");
        }
    }

    // 停止所有排行榜的定时任务
    public void stopAllTasks() {
        for (RankingTaskData rankingTaskData : rankingTasks.values()) {
            if (rankingTaskData.getTaskId() != -1) {
                Bukkit.getScheduler().cancelTask(rankingTaskData.getTaskId());
            }
        }
    }

    // 重载单个排行榜的配置和任务
    public void reloadRanking(String rankingId) {
        // 从配置重新加载间隔
        if (!TyRank.getRankingManager().getRankDataMap().containsKey(rankingId)) {
            plugin.getServer().getConsoleSender().sendMessage(StaticSystemVarConst.CONSOLE_PREFIX + ColorUtil.color("排行榜 " + rankingId + " 不存在"));
            return;
        }
        RankingData rankingData = TyRank.getRankingManager().getRankDataMap().get(rankingId);
        RankingTaskData newRankingTask = new RankingTaskData(rankingId, rankingData.getUpdateInterval());
        // 替换旧的排行榜对象
        rankingTasks.put(rankingId, newRankingTask);
        // 启动新任务
        startRankingTask(newRankingTask);
    }


    private void doRankEndAction(RankingData rankingData) {
        // 如果大于闭榜时间，判断rankData里是否存在该排行榜ID
        // 如果不存在，则新增一个一个
        // 如果存在的话判断这个对象的isEnd是否为false，如果为false，执行一次该排行榜的闭榜执行然后将isEnd修改为true!
        if (! TyRank.getRankDataManager().getRankDataMap().containsKey(rankingData.getId())) {
            TyRank.getRankDataManager().getRankDataMap().put(rankingData.getId(), new RankData(rankingData.getId(), false, DateUtil.currentDate()));
        }
        RankData rankData = TyRank.getRankDataManager().getRankDataMap().get(rankingData.getId());
        if(! rankData.getIsEnd()) {
            rankData.setIsEnd(true);
            // 修改rankData
            TyRank.getRankDataManager().saveRankData(Stream.of(rankData).collect(Collectors.toList()));
            // 重新加载一次该rankData
            TyRank.getRankDataManager().loadRankData(rankingData.getId());
            // 闭榜执行 - 消息
            String endMessage = TyRank.getConfigManager().getConfigData().getEndMessage();
            // 消息中包含了标签<rankName> <top1> <top2> <topn-n> ...
            // 先解析rankName和date
            endMessage = endMessage.replace(TagConst.RANK_NAME_TAG, rankingData.getRankName());
            endMessage = endMessage.replace(TagConst.DATE_TAG, DateUtil.currentDateStr());
            // 再解析同<top1>这样的标签
            Matcher topTagMatcher = PatternConst.TOP_TAG_PATTERN.matcher(endMessage);
            while (topTagMatcher.find()) {
                // 提取完整的<top>标签
                String topTag = topTagMatcher.group();
                // 提取数字
                String topGroup = topTagMatcher.group(1);
                int top = Integer.parseInt(topGroup);
                // 根据top和当前榜ID获取玩家并转换玩家名
                PlayerData currentTopPlayerData = TyRank.getPlayerDataManager().getPlayerDataByRankIdAndRanking(rankingData.getId(), top);
                String playerName = TyRank.getLanguageManager().getLanguageData().getGui().getTopNotAtTheMoment();
                if (null != currentTopPlayerData) {
                    playerName = TyRank.getPlayerDataManager().getPlayerName(currentTopPlayerData.getPlayerIdentity());
                }
                endMessage = endMessage.replace(topTag, playerName);
            }
            // 再解析同<top1-10> 这样的标签
            List<TopRangeResult> rangeResults = PatternUtil.extractAllRangeInfo(endMessage);
            if (ListUtil.isNotBlank(rangeResults)) {
                for (TopRangeResult rangeResult : rangeResults) {
                    // 组装排名字符串
                    List<String> playerNames = new ArrayList<>(rangeResult.getTops().size());
                    for (Integer top : rangeResult.getTops()) {
                        PlayerData currentTopPlayerData = TyRank.getPlayerDataManager().getPlayerDataByRankIdAndRanking(rankingData.getId(), top);
                        String playerName = TyRank.getLanguageManager().getLanguageData().getGui().getTopNotAtTheMoment();
                        if (null != currentTopPlayerData) {
                            playerName = TyRank.getPlayerDataManager().getPlayerName(currentTopPlayerData.getPlayerIdentity());
                        }
                        playerNames.add(playerName);
                    }
                    String finalPlayerNames = String.join(",", playerNames);
                    // 替换当前标签
                    endMessage = endMessage.replace(rangeResult.getFullTag(), finalPlayerNames);
                }
            }
            // 消息解析完成，发送BC
            Bukkit.broadcastMessage(endMessage);

            // 闭榜执行 - 动作
            // 获取闭榜动作执行
            List<String> playerActions = TyRank.getConfigManager().getConfigData().getEndPlayerAction();
            // 循环当前所有在线玩家
            for (Player onlinePlayer : Bukkit.getOnlinePlayers()) {
                // 每个玩家都执行动作
                // 动作里包含的标签：<rankName> <ranking>
                // 获取当前循环玩家在该榜中的排名
                PlayerData playerData = TyRank.getPlayerDataManager().getPlayerDataByRankId(onlinePlayer, rankingData.getId());
                String ranking = TyRank.getLanguageManager().getLanguageData().getMsg().getEndRankActionNotRanking();
                if(null != playerData) {
                    ranking = playerData.getRanking().toString();
                }
                for (String playerAction : playerActions) {
                    playerAction = playerAction.replace(TagConst.RANK_NAME_TAG, rankingData.getRankName()).
                            replace(TagConst.RANKING_TAG, ranking);
                    // 获取该执行对应的接口
                    ActionExecutor actionExecutor = ActionEnum.getExecutor(playerAction);
                    // 执行动作
                    actionExecutor.execute(playerAction, onlinePlayer);
                }
            }
        }
    }
}
